【译】Python 的下划线

原文:Underscores in Python

这篇博客讨论 Python 的_字符,我们将看到,和 Python 中很多东西一样,下划线很多用法在大多数情况(并不绝对)下是一种惯例。

单一下划线(_)

有典型的3种情况:

  1. 在解释器中: _ 存放的是交互式解释器会话中上一次执行的语句的结果。这个做法是标准 CPython 解释器最先采用的,其他的解释器也跟着采用了这种方式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> _
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    NameError: name '_' is not defined
    >>> 42
    >>> _
    42
    >>> 'alright!' if _ else ':('
    'alright!'
    >>> _
    'alright!'
  2. 作为名称(比如_shahriar): 这与上面那点有点联系,_作为临时性的名称使用,别人在读你的代码的时候就会知道,你分配了一个特定的名字,但之后不会再用。举个例子,你对下面循环中的实际值不感兴趣:

    1
    2
    3
    n = 42 
    for _ in range(n):
    do_something()
  3. 国际化:也许你看过_被当做函数使用,这种情况,通常用于实现国际化和本地化字符串之间的对应关系查找,这个似乎是源自、遵循相应的 C 语言惯例。
    例如,在Diango 翻译文档,你可以看到:

    1
    2
    3
    4
    5
    6
    from django.utils.translation import ugettext as _
    from django.http import HttpResponse

    def my_view(request):
    output = _("Welcome to my site.")
    return HttpResponse(output)

可以看到,第2点和第3点是可能冲突的,我们应该避免在国际化查找过程的代码块中同时使用_作为临时名称。

名称前的单下划线(如:_shahriar

对于程序员来说,名称以单一下划线开头,表明这个名称属性为私有。这个貌似是惯例,它让其他人(或自己)看到这些代码时知道以_开头的变量是用于内部使用的。正如 Python 文档中所述:

_为前缀的名称(如_spam)应该被视为 API 中非公开的部分(不管是函数、方法还是数据成员)。此时,应该将它们看做实现细节,修改时无需对外部通知。

这是一种惯例,因为它对解释器来说确实有一定意义,如果你 from <module/package> import *,以_开头的名称不会被导入,除非模块或包的__all__列表显式地写明了。可以看看import * in Python获得更多信息。

名称前的双下划线(如: __shahriar

名称(特指方法名)前使用双下划线(__)并不是一种惯例,对解释器来说它有特定的意义。Python 修饰这些名称用来避免与子类定义的名称冲突,Python 文档指出__spam这种形式(至少两个前导下划线,最多一个后续下划线)的任何标示符会被_classname__spam这种形式替换,在这里_classname是去掉前缀下划线的当前类名,例如:

1
2
3
4
5
6
7
8
>>> class A(object):
... def _internal_use(self):
... pass
... def __method_name(self):
... pass
...
>>> dir(A())
['_A__method_name', ..., '_internal_use']

正如所预料的,_internal_use并未改变,而__method_name被修饰为_ClassName__method_name,此时,如果你创建一个 A 的子类 B(呃,烂名字,烂名字,烂名字),你就不能轻易重载 A 类的 __method_name 方法了:

1
2
3
4
5
6
>>> class B(A):
... def __method_name(self):
... pass
...
>>> dir(B())
['_A__method_name', '_B__method_name', ..., '_internal_use']

这个行为几乎和 Java 的 final 方法以及 C++ 的标准方法(非虚方法)一样。

名称前后的双下划线(如:__init__

这是 Python 的特殊方法名,其实这是一个惯例,对于 Python 系统来说,它可以确保不会与用户定义的名称冲突,通常情况下,你需要覆盖这些方法,在里面实现你需要的功能,以便 Python 调用它们,例如当写一个类时,你通常需要定义__init__方法。

没有什么可以阻止你编写你自己的特殊方法名(但,请不要这么做):

1
2
3
4
5
6
>>> class C(object):
... def __mine__(self):
... pass
...
>>> dir(C)
... [..., '__mine__', ...]

尽量离这种方式的命名远一点,只让 Python 内部定义的特殊名称遵循这种方式。